// 用于学习父进程通过匿名管道控制子进程

#include <iostream>
#include <string.h>
#include <cassert>
#include <unistd.h>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include "Tash.hpp"

using namespace std;

const int gnum = 5; // 五个子进程，五个管道
Task t;             // 任务列表

class Endpoint
{
public:
    pid_t _child_id;
    int _write_fd; // 表明父进程要向哪一个管道里写入
public:
    Endpoint(pid_t id, int fd)
        : _child_id(id), _write_fd(fd)
    {
    }
    ~Endpoint()
    {
    }
};

void WaitCommand()
{
    while (true)
    {
        int command = 0;
        int n = read(0, &command, sizeof(int)); // 子进程读取键盘输入
        if (n == sizeof(int))
        {
            t.Execute(command); // 实现相应任务
        }
        else if (n == 0)
        {
            break;
        }
        else
        {
            break;
        }
    }
}

void CreateProcesses(vector<Endpoint> *end_points)
{
    vector<int> fds;
    // 1.先构建进程控制结构，由父进程写入，子进程读取
    for (int i = 0; i < gnum; i++)
    {
        // 创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n; // 无意义，仅用于避免未使用n导致的报错

        // 创建进程
        pid_t id = fork();
        assert(id != -1);

        // 子进程
        if (id == 0)
        {
            for(auto &fd:fds) close(fd);//删除上一次多的写端


            close(pipefd[1]);

            // 期望所有的子进程从原本的pipefd[0]读，改为从标准输入里读取
            // 因此重定向到 stdin
            dup2(pipefd[0], 0);

            WaitCommand(); // 等待指令

            close(pipefd[0]);
            exit(0);
        }

        // 父进程
        close(pipefd[0]);

        // 构建新的子进程和管道写入端对象，由end_points统一管理
        end_points->push_back(Endpoint(id, pipefd[1]));

        fds.push_back(pipefd[1]);//父进程每创建一个子进程，就保存本次打开写入端口
    }
}

void QuitProcess(vector<Endpoint> &end_points)
{
    // for (const auto &ep : end_points) // 这种方式执行会卡住
    // {
    //     close(ep._write_fd);
    //     waitpid(ep._child_id, nullptr, 0);
    // }
    //为什么会卡住？为什么采用双循环而不是单循环？解释见文档

    // 先让所有的子进程全部退出，需要让父进程关闭所有的写入端口，即fd
    for (const auto &ep : end_points)
        close(ep._write_fd);
    // 父进程回收子进程的僵尸状态
    for (const auto &ep : end_points)
        waitpid(ep._child_id, nullptr, 0);
    cout << "父进程回收子进程僵尸状态" << endl;



    // //单次循环可更改为如下方式
    // // for(int end = end_points.size() - 1; end >= 0; end--)
    // for(int end = 0; end < end_points.size(); end++)
    // {
    //     std::cout << "父进程让子进程退出:" << end_points[end]._child_id << std::endl;
    //     close(end_points[end]._write_fd);

    //     waitpid(end_points[end]._child_id, nullptr, 0);
    //     std::cout << "父进程回收了子进程:" << end_points[end]._child_id << std::endl;
    // } 
}

int main()
{
    vector<Endpoint> end_points; // 用于父进程管理
    CreateProcesses(&end_points);

    int num = 0;
    while (true)
    {
        // 1. 选择任务
        int command = COMMAND_LOG;

        // 2. 选择进程
        int index = rand() % end_points.size(); // 随机选择一个进程

        // 3. 下发任务
        write(end_points[index]._write_fd, &command, sizeof(command));

        sleep(1);
    }

    // 退出，回收所有的进程
    QuitProcess(end_points);
    return 0;
}
